#include <stdio.h>
#include <stdlib.h>
#include "worker.h"
#include "rand.h"
#include "exectuor.h"

__global__ void exectuor_run_device(Prover *prover,uint64_t *traget, int len)
{
    for(int i = 0; i < len; i++)
    {
        Exectuor e;
        e.setHash(prover->epoch_hash, prover->address);
        e.setProgram(prover->program);
        e.rand_input(i);
        e.run();

        uint64_t hash = e.calcHash(true);
        traget[i] = hash;
    }
}

__global__ void exectuor_run_inst_device(Prover* prover, const char* values)
{
    CuStringList params = CuString(values).split(" ");

    Exectuor e;
    e.setHash(prover->epoch_hash, prover->address);
    e.setProgram(prover->program);
    for (int i = 0; i < params.size(); i++)
    {
        CuVariant v = variantFromString(Public,params[i]);
        cuDebug() << "r" + CuString::number(i) + " =" << v;
        e.setReg(i, v);
    }
    e.run();

    //dest
    CuVariant dest = e.getReg(prover->program->inst[0].outOp);
    CuString dest_str = variantToString(dest);
    CuString dest_tips = "dest = " + dest_str;
    cuDebug() << dest_tips;

    //new var
    auto var_list = e.newVariable();
    CuStringList var_list_str;
    for (int i = 0; i < var_list.size(); i++)
        var_list_str.push_back(var_list[i].b.toString());

    CuString tips = CuString("private(") + CuString::number(var_list_str.size()) + "): [";
    if (var_list_str.size() <= 32)
        tips += cuStrJoin(var_list_str, ", ");
    else
        tips += cuStrJoin(var_list_str.mid(0, 16), ", ") + " ... " + cuStrJoin(var_list_str.mid(var_list_str.size() - 16), ", ");
    tips += "]";
    cuDebug() << tips;

    //hash
    uint64_t hash = e.calcHash(true);
    cuDebug() << "hash:" << hash;

    {
        extern bool check_rust(const char *dest, const char *variables, uint64_t hash);
        CuString line = cuStrJoin(var_list_str, ",");
        check_rust(dest_str.c_str(), line.c_str(), hash);
    }
}

__global__ void init_cuda_constant(int type, char *buffer)
{
    auto toField = [](char* buffer)->Field
    {
        Field f;
        return f;
    };

    auto toVec = [](char* buffer, CuVector<CuVector<Field>>& vec, int row, int col) {
        vec.resize(row);
        for (int i = 0; i < row; i++)
        {
            vec[i].resize(col);
            for (int j = 0; j < col; j++)
            {
                uint256_t b;
                memcpy(b.data, buffer, 32);
                buffer += 32;

                vec[i][j] = Field::constant(b);
            }
        }
    };

    if (type == Constant_P)
    {
        P::MODULUS = new uint256_t();
        P::EDWARDS_A = new uint256_t();
        P::EDWARDS_D = new uint256_t();
        
        memcpy(P::MODULUS->data,buffer,32);
        buffer += 32;

        memcpy(P::EDWARDS_A->data,buffer,32);
        buffer += 32;

        memcpy(P::EDWARDS_D->data,buffer,32);
        buffer += 32;
    }
    else if (type == Constant_Psd2)
    {
        psd2_domain = new Field();
        psd2_ark = new CuVector<CuVector<Field>>();
        psd2_mds = new CuVector<CuVector<Field>>();

        psd2_domain->b = 0;
        toVec(buffer,*psd2_ark,39,3);
        buffer += 39 * 3 * 32;
        
        toVec(buffer,*psd2_mds,3,3);
        buffer += 3 * 3 * 32;
    }
}

/*
void init_constant(int type, char* buffer,int nBytes)
{
    char *d_buffer;
    cudaMalloc((char**)&d_buffer, nBytes);
    cudaMemcpy(d_buffer, buffer, nBytes, cudaMemcpyHostToDevice);

    init_cuda_constant<<<1, 1>>>(type, d_buffer);

    cudaFree(d_buffer);
}

void exectuor_run(Prover* prover, uint64_t* traget, int num)
{
    dim3 block (num);
    dim3 grid  (1);

    size_t nBytes = num * sizeof(uint64_t);// malloc host memory
    uint64_t *d_traget;
    Program *d_program;
    
    cudaMalloc((uint64_t**)&d_traget, nBytes);
    cudaMalloc((Program**)&d_program, sizeof(Program));
    
    //cudaMemcpy(d_program, program, sizeof(Program), cudaMemcpyHostToDevice);

    //exectuor_run_device<<<grid, block>>>(d_program,num,d_traget);
    cudaMemcpy(traget, d_traget, nBytes, cudaMemcpyDeviceToHost);

    cudaFree(d_traget);
    cudaFree(d_program);

    cudaDeviceSynchronize();
}
*/

void init_constant(int type, char* buffer, int nBytes)
{
    init_cuda_constant(type, buffer);
}

void exectuor_run(Prover* prover, uint64_t* traget, int num)
{
    exectuor_run_device(prover,traget,num);
}

void exectuor_run_inst(Prover* prover, const char* values)
{
    exectuor_run_inst_device(prover, values);
}